#ifndef CALCULATOR_H
#define CALCULATOR_H

#include "MathExpress.h"
#include "MathErr.h"
#include <stack>
#include <sstream>
//#define ERROR_NUMBER (-327361)

class calculator
{
private:
    /**
     * @brief stack storing operants
     * 
     */
    std::stack<char> operants;

    /**
     * @brief stack storing numbers
     * 
     */
    std::stack<double> numbers;

    /**
     * @brief take in an operant and apply according actions to the stacks
     * 
     * @param c 
     */
    void CheckOperants(char c);

    /**
     * @brief Get the Order oft an operant c
     * 
     * @param c 
     * @return int 
     */
    int getOrder(char c);

    /**
     * @brief take in an operant and do single calculation to the top two numbers in the 'number' stack
     * 
     * @param c 
     */
    void Single_Calculation(char c);

public:
    /**
     * @brief Construct a new calculator object
     * 
     */
    calculator() : operants(), numbers() {}

    /**
     * @brief Destroy the calculator object
     * 
     */
    ~calculator() {}

    /**
     * @brief calculate the math expression
     * 
     * @param exp 
     * @return the result of calculation
     */
    double calculate(const MathExpress &exp);

    /**
     * @brief verify whether a string can be converted into a valid double type
     * 
     * @param number 
     * @return true 
     * @return false 
     */
    bool IsValidNumber(const std::string &number);
};

double calculator::calculate(const MathExpress &exp)
{
    //clear two stacks
    std::stack<char>().swap(operants);
    std::stack<double>().swap(numbers);

    //define a string consisting of valid operants
    std::string operators("+-*/()");

    std::string express(exp.getExpression());

    if (express.size() == 0)
        throw EmptyExpressionErr();

    unsigned int previous_position = 0;

    /**
     * @brief the position of the next operant
     * 
     */
    unsigned int position = express.find_first_of(operators);

    while (previous_position < express.size())
    { //the reading of the expression hasn's been finished
        if (position != std::string::npos)
        { //there is still an operant in the expression
            if (position > previous_position)
            { //there is a number between the last and the present operants
                std::string Number_tmp = express.substr(previous_position, position - previous_position);
                if (IsValidNumber(Number_tmp))
                {
                    numbers.push(stod(Number_tmp));
                }
            }
            else
            { //the two operants are adjacent
                char operant = express[position];
                CheckOperants(operant);
                position++;
            }
        }
        else
        { //the remaining part of the expression is a number
            numbers.push(stod(express.substr(previous_position)));
            break;
        }

        previous_position = position;
        position = express.find_first_of(operators, previous_position);
    }

    //pop the remaining operants
    while (!operants.empty())
    {
        if (operants.top() == '(')
        {
            throw BracketsMatchErr();
        }
        Single_Calculation(operants.top());
        operants.pop();
    }

    double result = numbers.top();
    numbers.pop();

    if (!numbers.empty())
    {
        throw OperantsMatchErr();
    }

    return result;
}

int calculator::getOrder(char c)
{
    switch (c)
    {
    case '(':
        return 1;
    case '+':
    case '-':
        return 2;
    case '*':
    case '/':
        return 3;
    case ')':
        return 4;
    default:
        return 0;
    }
}

void calculator::Single_Calculation(char c)
{
    double a, b;
    if (!numbers.empty())
    {
        b = numbers.top();
        numbers.pop();
    }
    else
    {
        throw OperantsMatchErr();
    }

    if (!numbers.empty())
    {
        a = numbers.top();
        numbers.pop();
    }
    else
    {
        throw OperantsMatchErr();
    }

    switch (c)
    {
    case '+':
        numbers.push(a + b);
        break;
    case '-':
        numbers.push(a - b);
        break;
    case '*':
        numbers.push(a * b);
        break;
    case '/':
        if (b == 0)
            throw ZeroDivideErr();
        numbers.push(a / b);
        break;

    default:
        break;
    }
}

void calculator::CheckOperants(char c)
{
    switch (c)
    {
    case '(':
        operants.push(c);
        break;
    case ')':

        //pop all operants between '(' and ')'
        while (!operants.empty() && operants.top() != '(')
        {
            Single_Calculation(operants.top());
            operants.pop();
        }

        if (operants.empty())
        {
            throw BracketsMatchErr();
        }
        else
        { //the top operant is '('
            operants.pop();
        }
        break;
    case '+':
    case '-':
    case '*':
    case '/':
        if (operants.empty())
        {
            operants.push(c);
        }
        else
        {
            while (getOrder(operants.top()) >= getOrder(c))
            {
                Single_Calculation(operants.top());
                operants.pop();
            }
            operants.push(c);
        }
        break;
    default:
        break;
    }
}

bool calculator::IsValidNumber(const std::string &number)
{
    //check the number of points
    int Num_Of_Points = count(number.begin(), number.end(), '.');
    if (Num_Of_Points > 1)
    {
        throw PointsNumberErr();
        return false;
    }

    return true;
}

#endif